// utf8.cpp
// Basic functions for handling UTF-8 encoded text. Note that you can
// also easily read out Unicode code-points, which are 32-bit unsigned
// integers.
//
// Copyright (C) 2008  Maksim Sipos <msipos@mailc.net>
//
// This file is a part of the Ripe language project.
//
// Ripe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "util/utf8.hpp"

// Some bit-sequence macros (to make this code more maintainable).
#define B_00000111 0x7
#define B_00001111 0xF
#define B_00011111 0x1F
#define B_00111111 0x3F
#define B_10000000 0x80
#define B_11000000 0xC0
#define B_11100000 0xE0
#define B_11110000 0xF0
#define B_00000111_11000000 0x7C0
#define B_00001111_11000000 0xFC0
#define B_11110000_00000000 0xF000
#define B_00011100_00000000_00000000 0x1C0000
#define B_00000011_11110000_00000000 0x3F000

// Tells you if the given octet is in the ascii range (Unicode
// 00 through 7F).
#define r_utf8_is_ascii(c) (!(0x80 & (c)))

// Tells you if the given octet is in the middle of a UTF-8 encoded
// character.
#define r_utf8_in_middle(c) (((c) & 0xC0) == 0x80)

// Tells you if the given octet is starting a 2-character UTF-8
// encoded sequence (Unicode 0080 through 07FF).
#define r_utf8_is_start2(c) (((c) & 0xE0) == 0xC0)

// Tells you if the given octet is starting a 3-character UTF-8
// encoded sequence (Unicode 0800 through D7FF and E000 through FFFF).
#define r_utf8_is_start3(c) (((c) & 0xF0) == 0xE0)

// Tells you if the given octet is starting a 4-character UTF-8
// encoded sequence (Unicode 010000 through 10FFFF).
#define r_utf8_is_start4(c) (((c) & 0xF8) == 0xF0)

// Tells you how many octets are needed to encode the given #uni_char.
// This assumes a valid uni_char (r_utf_valid_uni_char()).
#define r_utf8_needs(u) (((u) <= 0x7F) ? 1 : \
                          (((u) <= 0x7FF) ? 2 : \
                           (((u) <= 0xFFFF) ? 3 : 4) \
                          ) \
                         )

// Verifies if a UniChar is not bigger than the allowed Unicode
// range. This does not check for the Unicode range D800 through DFFF,
// which is not allowed (but correctly encoded using UTF-8).
#define r_utf8_valid(u) ((u) <= 0x10FFFF)

namespace Ripe {

UniChar read_uni_char(const char** str, const char* limit)
{
  // TODO: Current implementation seems clear, yet inefficient. Eventually
  // it should be improved.
  assert(str != NULL); assert((*str) != NULL);

  // str_t will be used to scan through string, and eventually update str.
  // However, str is only updated if UTF-8 is encoded correctly.
  uint8_t* str_t = (uint8_t*) *str, *limit_t = (uint8_t*) limit;

  // These will be used to construct parts of dest from the individual
  // octets.
  UniChar w, x, y, z;

  // Other 3 octets.
  uint8_t* str_t2 = str_t + 1, *str_t3 = str_t + 2, *str_t4 = str_t + 3;

  // Few sanity checks:
  if (str_t >= limit_t) throw ErrorUTF8Limited();
  if (r_utf8_in_middle(*str_t)) throw ErrorUTF8Seek();

  if (r_utf8_is_ascii(*str_t)) {
    // 1 octet:
    (*str)++;
    return (UniChar) (*str_t);

  } else if (r_utf8_is_start2(*str_t)) {
    // 2 octets: 110yyyyy 10zzzzzz => yyyyyzzzzzz
    if ((str_t + 1) >= limit_t) throw ErrorUTF8Limited();
    if (not r_utf8_in_middle(*str_t2)) throw ErrorUTF8Invalid();
    y = (*str_t) & B_00011111;
    z = (*(str_t2)) & B_00111111;
    (*str) += 2;
    return (y << 6) | z;

  } else if (r_utf8_is_start3(*str_t)) {
    // 3 octets: 1110xxxx 10yyyyyy 10zzzzzz => xxxxyyyy yyzzzzzz
    if ((str_t + 2) >= limit_t) throw ErrorUTF8Limited();
    if (!r_utf8_in_middle(*str_t2) or !r_utf8_in_middle(*str_t3))
      throw ErrorUTF8Invalid();
    x = (*str_t) & B_00001111;
    y = (*str_t2) & B_00111111;
    z = (*str_t3) & B_00111111;
    (*str) += 3;
    return (x << 12) | (y << 6) | z;

  } else if (r_utf8_is_start4(*str_t)) {
    // 4 octets: 11110www 10xxxxxx 10yyyyyy 10zzzzzz => wwwxx xxxxyyyy yyzzzzzz
    if ((str_t + 3) >= limit_t) throw ErrorUTF8Limited();
    if (!r_utf8_in_middle(*str_t2) or !r_utf8_in_middle(*str_t3)
        or !r_utf8_in_middle(*str_t4))
      throw ErrorUTF8Invalid();
    w = (*str_t) & B_00000111;
    x = (*str_t2) & B_00111111;
    y = (*str_t3) & B_00111111;
    z = (*str_t4) & B_00111111;
    (*str) += 4;
    return (w << 18) | (x << 12) | (y << 6) | z;

  }
  throw ErrorUTF8Invalid();
}

void write_uni_char(char** str, const char* limit, UniChar c)
{
  assert(str != NULL); assert((*str) != NULL);

  if (not r_utf8_valid(c)) throw ErrorUTF8Invalid();
  int bytes = r_utf8_needs(c);
  uint8_t *str_t = (uint8_t*) *str;
  // Other 3 octets:
  uint8_t *str_t2 = str_t + 1, *str_t3 = str_t + 2, *str_t4 = str_t + 3;

  if (str_t + bytes > (uint8_t*) limit) throw ErrorUTF8Limited();
  switch(bytes){
    case 1:
      *str_t = (uint8_t) c;
      (*str)++;
      break;
    case 2:
      // 2 octets: yyy yyzzzzzz => 110yyyyy 10zzzzzz
      *str_t = ((B_00000111_11000000 & c) >> 6) | B_11000000;
      *str_t2 = (B_00111111 & c) | B_10000000;
      (*str) += 2;
      break;
    case 3:
      // 3 octets: xxxxyyyy yyzzzzzz => 1110xxxx 10yyyyyy 10zzzzzz
      *str_t = ((B_11110000_00000000 & c) >> 12) | B_11100000;
      *str_t2 = ((B_00001111_11000000 & c) >> 6) | B_10000000;
      *str_t3 = (B_00111111 & c) | B_10000000;
      (*str) += 3;
      break;
    case 4:
      // 4 octets: 000wwwxx xxxxyyyy yyzzzzzz
      //           => 11110www 10xxxxxx 10yyyyyy 10zzzzzz
      *str_t = ((B_00011100_00000000_00000000 & c) >> 18) | B_11110000;
      *str_t2 = ((B_00000011_11110000_00000000 & c) >> 12) | B_10000000;
      *str_t3 = ((B_00001111_11000000 & c) >> 6) | B_10000000;
      *str_t4 = (B_00111111 & c) | B_10000000;
      (*str) += 4;
      break;
    default:
      assert_never();
  }
}

}
